package com.zwh.se.design_pattern.creational.singleton;

import java.io.*;
import java.lang.reflect.Constructor;

/**
 * 通过枚举来实现的单例模式。<p>
 * 这种方式是 《Effective Java》 作者 Josh Bloch 提倡的方式。
 * 它不仅能避免多线程同步问题，而且还自动支持序列化机制，同时防止反序列化重新创建新的对象，绝对防止多次实例化。
 * 还不能通过 反射 来调用私有构造方法。
 * 是真正意义上的最完美的单例模式。
 * <p>
 *
 * @author yexi
 */
public enum EnumSingleton {

    //
    INSTANCE;

    public static void main(String[] args) throws InterruptedException, IOException, ClassNotFoundException {
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                System.out.println(EnumSingleton.INSTANCE.hashCode());
            }).start();
        }
        Thread.sleep(1000);

        //为什么枚举类可以阻止反射的破坏?
        /**
         * 1. 表面原因: 首先自己创建的枚举类是没有显式构造方法的。即便是父类的Enum类中也是没有空参构造方法的,只有一个带两个参数的构造方法。
         * @see Enum#Enum(String, int)
         * 2. 真正原因: 反射方法中不允许使用反射创建枚举对象.
         * @see Constructor#newInstance(Object...)
         *        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
         *           //抛出异常: 不能使用反射方式创建enum对象
         *             throw new IllegalArgumentException("Cannot reflectively create enum objects");
         */
        try {
            Class<EnumSingleton> clazz = EnumSingleton.class;
            Constructor<EnumSingleton> c = clazz.getDeclaredConstructor(String.class, int.class);
            //设置为true,就可以对类中的私有成员进行操作了
            c.setAccessible(true);
            Object instance1 = c.newInstance();
            Object instance2 = c.newInstance();
            System.out.println(instance1 == instance2);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //为什么枚举类可以阻止序列化的破坏?
        // Java规范规定，每个枚举类型及其定义的枚举变量在JVM中都是唯一的，因此在枚举类型的序列化和反序列化上，Java做了特殊的规定:
        // 在序列化的时候Java仅仅是将枚举对象的name属性输到结果中，反序列化的时候则是通过java.lang.Enum的valueOf()方法来根据名字查找枚举对象。
        // 比如说，序列化的时候只将 INSTANCE 这个名称输出，反序列化的时候再通过这个名称，查找对应的枚举类型，
        // 因此反序列化后的实例也会和之前被序列化的对象实例相同。
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile.obj"));
        oos.writeObject(INSTANCE);
        File file = new File("tempFile.obj");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        EnumSingleton singleton = (EnumSingleton) ois.readObject();
        System.out.println(singleton);
        System.out.println(INSTANCE);
        //判断是否是同一个对象
        System.out.println(INSTANCE == singleton);


    }
}
